
//方法的使用 - UseOfMethods

//什么是方法？
//方法就是一个代码片段. 类似于 C 语言中的 "函数".
// （其实方法就是函数另一个称呼，只不过为了区分 C语言和Java，所以在Java中叫方法，C叫函数，唯一不同的就是两者实现的方式）
// 方法可以理解为是一个 功能，实现某种我们想要达到的效果，而且这个功能是可以被重复使用的。
// 打个比方： 方法就好像是 制作面包的磨具，如果你想得到一个这样形状，或者说达到某种效果，你就去使用该磨具。（该函数能完成某一个 功能/任务/效果）
//而且，你可以换材料，但你不能用它做其他的东西，这个磨具是专门正对面包的（函数实现的功能，是专门针对某一种情况）
// 也就是说你做面包要使用材料必须属于面粉这一类的东西，无非即使质量的好坏
// （函数接收的参数值是可以改变大小的，类型不能变，否则参数类型不匹配，是无法成功调用函数的功能）
// 方法存在的意义(不要背, 重在体会):
//        1. 是能够模块化的组织代码(当代码规模比较复杂的时候).
//        2. 做到代码被重复使用, 一份代码可以在多个位置使用.
//        3. 让代码更好理解更简单.
//        4. 直接调用现有方法开发, 不必重复造轮子

//方法的语法格式
//public static 返回值 方法名称（形式参数列表）{
//  函数体/方法体
//}



// 代码实例
// 求 1~n的和
//import java.util.Scanner;

//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner scanner = new Scanner(System.in);// 为输入数据做准备
//        int n = scanner.nextInt();// 接收一个整型数据，将其赋给 n
//        sumadd(n);// 调用函数（方法）sumadd，并向 sumadd方法 传参（n）。
//    }
//    public static void sumadd(int n){// 这里接收参数，和C语言一样，可以定义一个相同的变量名，来接收传参数。但必须类型和参数个数要相同
//        int sum =0;// 定义一个求和变量，将其初始化
//        for(int i =0;i<=n;i++){
//            sum += i;// 将1~10的每个数值累加起来
//        }
//        System.out.println(sum);// 输出累加的结果
//    }
//}// 图1
// 不知道你们有没有发现程序中 我们的 sumadd方法没有写在 main方法上面，而是下面，学过C的朋友可能会问 “不需要在前面声明函数吗？”
// 答案是不用，在Java中 是没有函数声明的概念，它就是要用的时候，直接去调用
// 另外在普及一个内容，方法的参数叫做形参，main方法的参数叫实参，形参相当于实参的一份拷贝，这种传参方式被称为 按值传递（传值）
// 由此引申出一个知识点， 在Java中 是没有 传址 的概念，只有传值，所以我们在调用方法的时候，要注重 形参 和 实参 的类型 和 个数 是否匹配（相同）

//如果你想让方法（函数）返回一个值，就是说带回一个值回到主函数，再对其打印也行
// 代码如下
//import java.util.Scanner;
//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner scanner = new Scanner(System.in);// 为输入数据做准备
//        int n = scanner.nextInt();// 接收一个整型数据，将其赋给 n
//        int ret = sumadd(n);// 调用方法（函数）sumadd，并想 sumadd方法 传参（n）。
//        System.out.println(ret);
//    }
//    public static int sumadd(int n){// 这里接收参数，和C语言一样，可以定义一个相同的变量名，来接收传参数。但必须类型和参数个数要相同
//        int sum =0;// 定义一个求和变量，将其初始化
//        for(int i =0;i<=n;i++){
//            sum += i;// 将1~10的每个数值累加起来
//        }
//        return sum;
//    }
//}// 图2,注意圈起来的部分
// 有的可能会说 我可不可以 不创建一个 与方法相同类型的变量 来接收它的值，直接输出行吗？
// 答案是可以的，写法： System.out.println(sumadd(n));
// 因为 sumadd 是返回值的，输出语句，只是将它返回来的值给打印了而已。
// 如果方法的返回值为 void（无返回），那么程序会报错（输出语句会说：我都准备好了，你就给我看这？ 就这！）




//既然讲到函数（方法），也就会涉及函数栈帧问题，
// 每个函数，在被调用的时候，都会开辟栈帧（开辟属于自身的内存空间）
// 函数栈帧图，可以 c方面的可以看这篇文章
//注意事项
//        1. public 和 static 两个关键字在此处具有特定含义, 我们暂时不讨论, 后面会详细介绍.
//        2. 方法定义时, 参数可以没有. 每个参数要指定类型
//        3. 方法定义时, 返回值也可以没有, 如果没有返回值, 则返回值类型应写成 void
//        4. 方法定义时的参数称为 "形参", 方法调用时的参数称为 "实参".
//        5. 方法的定义必须在类之中, 代码书写在调用位置的上方或者下方均可.
//        6. Java 中没有 "函数声明" 这样的概念



//方法调用的执行过程
//基本规则
//        定义方法的时候, 不会执行方法的代码. 只有调用的时候才会执行.
// 比如：int b = sumadd();，它在执行该语句时，遇见方法调用，会先去执行调用方法，下面程序它暂时不会去关。
// System.out.println(b); 调用方法的代码执行完了之后，才会轮到该语句执行

//        当方法被调用的时候, 会将实参赋值给形参.（int a = 10; sumadd(a); public static void sumadd(int A); 这里的A,其实就是a值的一份拷贝  ）
//        参数传递完毕后, 就会执行到方法体代码.
//        当方法执行完毕之后(遇到 return 语句), 就执行完毕, 回到方法调用位置继续往下执行
//        一个方法可以被多次调用.


// 代码示例: 计算 1! + 2! + 3! + 4! + 5!

//public class UseOfMethods {
//    public static void main(String[] args) {
//        int n  =5;
//        int sum = factorialSum(n);// 调用函数计算 5 的阶乘和是多少，将其赋给sum
//        System.out.println(sum);
//    }
//    public  static  int factorialSum(int n){
//        int sum = 0;
//        int  ret = 1;
//        for(int i = 1;i<=n;i++){
//            ret *= i;// 每个数字的阶乘
//            sum+=ret;// 将每个数字的阶乘累计相加
//        }
//        return sum;
//    }// 图 4
//}
// 使用方法的优点也就体现出来了，创建一个专门计算阶乘和的函数，只要我的传参没问题，它就能帮我完成想要的效果
// 注意 自定义方法名，要让人看得懂是什么意思，见名知其用。看到名字知道它是用来干什么的，接下看代码，往这个思想靠拢
// 大大提升代码的阅读性。


//实参和形参的关系
//局个例子（交换a与b值）
// 我们先来用常规形式来写
//这里是交换实参
//public class UseOfMethods {
//    public static void main(String[] args) {
//        int a = 10;
//        int b = 20;
//        System.out.println("交换前：a = " + a + " b = " + b);
////        1
////        int tmp =a;
////        a =b;
////        b = tmp;// 图 5
//
//        //2
//        a = a^b;
//        b = a^b;
//        a = b^a;// 图 6
//        // 两种方法均可以交换两者数据
//        System.out.println("交换后：a = " + a + " b = " + b);
//    }
//}


// 方法
//public class UseOfMethods {
//    public static void main(String[] args) {
//        int a = 10;
//        int b = 20;
//        System.out.println("交换前：a = " + a + " b = " + b);
//        swap(a, b);
//        System.out.println("交换后：a = " + a + " b = " + b);
//    }
//    public static void swap(int x, int y) {
//        int tmp = x;
//        x = y;
//        y = tmp;
//    }
//
//}//由图 7，8.可知这样写不会发生任何改变，因为只是交换形参，也就是说 交换的是 swap的参数
// 并不是在交换 main方法的参数，这里的x，y，只是a和b的值的一份拷贝
// 有人肯定会说像C里面一样，传地址呗
// 记住 在Java中，是无法取得栈上变量的地址的，也就是说取不到局部变量的地址
// 图 9

// 如果要去做，只能把 a 和 b 的值，放到堆上（动态空间上）。放在堆上的都是对象。
// 这个留着 我们讲 对象和类，或者数组的时候，再进行讲解。以我们现在的基础还不足以解决该问题。



//没有返回值的方法
//方法的返回值是可选的. 有些时候可以没有的

// 代码示例
//public class UseOfMethods {
//    public static void main(String[] args) {
//        int a = 0;
//        int b = 1;
//        sum(a,b);
//    }
//    public static void sum(int x,int y){
//        System.out.println(x+y);
//        // 我在方法中就已经将我们想打印的结果 图 10，所以不需要返回值
//        //但是规定 无返回，就不能 return 返回一个值
////        return x+y; 图 11，都报错了，就别谈运行
//        // 但是可以这么写
//        return ;// 虚晃一枪，你能奈我何？
//        // 图 12
//    }
//}


// 方法的重载
//有些时候我们需要用一个函数同时兼容多种参数的情况, 我们就可以使用到方法重载

//代码示例

//public class UseOfMethods {
//    public static void main(String[] args) {
//        int a = 10;
//        int b = 20;
//        int ret = add(a, b);
//        System.out.println("ret = " + ret);
//        double a2 = 12.5;
//        double b2 = 18.5;
//        double ret2 = add(a2, b2);//图 13
//        System.out.println("ret2 = " + ret2);
//    }
//    public  static  int add(int x,int y){
//        return  x+y;
//    }
//}

// 由此，我们需要 方法的重载：一个函数同时兼容多种参数的情况
// 那我们怎么实现呢？
// 代码实例2
//public class UseOfMethods {
//    public static void main(String[] args) {
//        int a = 10;
//        int b = 20;
//        int ret = add(a, b);
//        System.out.println("ret = " + ret);
//        double a2 = 12.5;
//        double b2 = 18.5;
//        double b3 = 9.0;
//        double ret2 = add(a2, b2,b3);//图 13
//        System.out.println("ret2 = " + ret2);
//    }
//    public  static  int add(int x,int y){
//        return  x+y;
//    }
//    public static double add(double x,double y,double z){
//        return (x+y+z);
//    }
//}// 图 14


// 记住方法重载的重点在于函数名要相同，参数个数或者类型要不同。（两者中必有一者不同）
// 而返回值 不重要，只要你接收返回的值的类型是相匹配的就行，要不然无法进行赋值。
// 另外切记 ，不要写的一摸一样（参数个数和类型都相同），这样相当于 一个变量被重复定义，是会出错的
// 例如：
//    public  static  int add(int x,int y){
//        return  x+y;
//    }

//    public  static  int add(int x,int y){
//        return  x+y;
//    }


// 另外这样写也不行，正如我前面所说的 返回值，不是重点，参数类型和个数才是重点
// 只要你参数的类型 和 个数 一模一样，无论你返回值是什么，都算方法的重定义（方法的重定义这是我个人理解）
// 程序是会报错的
//public class UseOfMethods {
//    public static void main(String[] args) {
//        int a = 10;
//        int b = 20;
//        int ret = add(a, b);
//        System.out.println("ret = " + ret);
//    }
//    public  static  int add(int x,int y){
//        return  x+y;
//    }
//    public static double add(int x,int y){
//       return (double)(x+y);
//    }
//}// 图 15



// 由此我看出 方法的重载 有多么强大。
// 我们可以根据 参数的类型和个数 来为我们的方法，加载 几个新的方法
//图 16

// 现在你知道了吧 方法的重载，有多爽！

// 补充一个知识点：如果一个类的两个方法（无论是在同一个类中声明，还是都由一个类继承的，或者一个声明和一个继承的【总的来说不一定是同一个类里的】）具有相同的名称
//但签名不是重写等价的，则称该方法名为重载。 （了解即可）

// 由上我可以总结出 方法重载的规则
// 方法名要相同
// 方法的参数不同（参数个数或者类型，两者选其一，或者都选，反正至少有一个因素是不同的）
// 方法的返回值类型不影响重载
// 当两个方法的名字相同, 参数也相同, 但是返回值不同的时候, 不构成重载.



// 方法递归
//递归的概念
//一个方法在执行过程中调用自身, 就称为 "递归".
// 递归相当于数学上的 "数学归纳法", 有一个起始条件, 然后有一个递推公式
// 递推公式是递归的重点，推出它，递归就很好写。没推出。就。。。嗯~~~
// 使用递归之前，需要一个前提
// 1. 有一个趋近于终止的条件（停止递归的条件）
// 2，自己调用自己（让它自己扇自己，扇疼了再停下来，不要问我为什么我举这个例子！我恨它）

//代码示例
//public class UseOfMethods {
//    public static void main(String[] args) {
//        func();
//    }
//
//    // 这样写，就不满足 使用递归所需的第一个条件 没有一个终止递归的条件
//    // 所以该程序会无限递归死循环，最终导致栈溢出（栈空间不是无限大，是有限，func方法一直调用自己下去，最终肯定是会 爆满/溢出 的）
//    因为 每次调用 func方法时，都会为其在栈上空间开辟块自己的空间
//    public  static void func(){
//        func();
//    }
//}// 图17

// 那我该怎么去写一个可以使用的递归呢？
// 代码实例2
//public class UseOfMethods {
//    public static void main(String[] args) {
//        func(3);
//    }
//    public  static void func(int n){
//        if(n==1){// 递归的终止的条件，因为 n == 3.每次自己调用自己时，n都会减1，调用 3 次，就结束了
//            System.out.println(n);
//            return ;
//        }
//        func(n-1);
//        System.out.println(n);
//    }
//}// 图18
// 其实终止条件，相当于数学里的起始条件
// 你这样想 假设我们不知道 n 为多少，那我们怎么办？先从初始情况分析呗
// 当 n =0时，怎么办？
// n =1时，怎么办？
// 大于1又怎么办？
// en~，是不是有点懂了，当我们处理递归时，我们可以有两种思维
// 它的终止条件是什么？ 你可能会被套娃，套成zz。
// 这时候，我们反过来想想 这个值的初始值是多少，满足怎样的条件？（就比如说要大于，小于或等于某个条件）
// 是不是打开新思路了？


//思考递归的时候，横向思考：根据递推公式（个人理解递推公式跟数学的通项公式有点像）去思考
//代码执行：纵向执行的（从上往下，一条条执行）

// 在坐的某些人，经常会自己代入某个数字，然后，拿着数字和代码一个个展开，很烧脑的
// 虽然我很喜欢这样，但是我只是在我掌握递归规律（递推公式）之后，对代码执行过程的好奇，去展开很小很小的范围。比如1、2、3之类的
// 太复杂了，对不起，我不玩了。。

// 这点不值得提倡，我需要掌握其递归规律，才是重中之重。


// 举个例子
// 求 n的阶乘(n==5)
// 1! == 1 //这就是我们的起始条件，也是我们的终止条件
// 2! == 2*1 == 2 * 1!
// 3! == 3*2*1 == 3 * 2!
// 4! == 4*3*2*1 == 4 * 3!
// 5! == 5*4*3*2*1 == 5 * 4!
// 你发现了 求 5！的值，它是 5 * （5-1）！
// 而  4！ == 4 * (4-1)！
//    3!  == 3 * (3-1)!
//    2!  == 2 * （2-1）！
// 以此类推 直到遇到 1！ 时，他返回值，不再带其他阶乘，此时 1！是不是很适合作为我们递归的终止条件

// 由此我们发现了 递推公式为 n * （n-1）!
// 5! == 5 * 4!== 5 * 4 * 3! == 5 * 4 * 3 * 2! == 5 * 3 * 2 * 1(这里只是帮助你们理解，别学，上面的递推公式才是你们该学的)

// 代码实现

//import java.util.Scanner;
//
//public class UseOfMethods{
//    public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);
//        int n = sc.nextInt();
//        System.out.println(factorial(n));
//    }
//    public  static  int factorial(int n){
//        if(1==n){
//            return 1;
//        }
//        return n*factorial(n-1);// n * (n-1)! , 括号里的n: 是 编译器自动补上去的。不用管。
//        // (n-1)! == factorial(n-1);  即 factorial == ！
//    }
//
//}// 图19，不懂没关系，我帮你搬下来，对着看
// 1! == 1 //这就是我们的起始条件，也是我们的终止条件
// 2! == 2*1 == 2 * 1!
// 3! == 3*2*1 == 3 * 2!
// 4! == 4*3*2*1 == 4 * 3!
// 5! == 5*4*3*2*1 == 5 * 4!
// 你发现了 求 5！的值，它是 5 * （5-1）！
// 而  4！ == 4 * (4-1)！
//    3!  == 3 * (3-1)!
//    2!  == 2 * （2-1）！

// 5! == 5 * 4!== 5 * 4 * 3! == 5 * 4 * 3 * 2! == 5 * 3 * 2 * 1
// 程序运行 跟我们刚才藏宝殿是一样的，一层一层的抢。从最里面的开始抢
// 也就从我们终止条件开始
//   n == 1;  return 1;// 这里的返回值 是返回到 调用它的上一层级（藏宝殿核心是第一层吗，抢完了，肯定去抢第二程啊）
//   n==2    return 2* factorial(2-1) == 2 * 1 == 2
//   n == 3;  return 3 * 2 == 6
//   n == 4;  return 4 * 6 == 24
//   n == 5;  return 5 *   ==
// 考验你们的时候，在下面评论，看你们到底有没有看懂学会


// 递归 n * factorial(n-1)l  n-1 就是它传过去的值，称为递， factorial（n-1）返回的值，称为归
// 结合称为： 递归。
// 你赚的钱迟早是有一天会花完的。。。
// 赶快跟着我一起卷。



// 求 1~n之间的和

//import java.util.Scanner;
//
//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);
//        int n = sc.nextInt();
//        System.out.println(sumadd(n));// 图 20
//    }
//    public static  int sumadd(int n){
//        if(n==1){
//            return 1;
//        }
//        return n + sumadd(n-1);
//    }
//}// 自己琢磨，不懂在下方评论，一起探讨。（n 比 n-1 多了1，看似废话，其实是真理）
// 图 20


//import java.util.Scanner;
//
//// 按照顺序打印一个数字的每一位（例如1234 打印出 1 2 3 4）
//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);
//        int n = sc.nextInt();
//        print(n);
//    }
//    public  static  void print(int n){
//        if(n<10){
//            System.out.println(n);
//            return;
//        }
//        print(n/10);// 在调用自身是，将 n/10 的值 作为下个自身函数的形参
//        // 最终肯定有 剩一位数的时候，一位数（0~9）肯定是小于10的，别问为什么，问就不会！
//        // 这个也就是我们终止条件
//        // 假设我们输入的是 1234 ， 在调用自身是，将 n/10 的值 作为下个自身函数的形参，那么 剩余的最后一位就是 1.所以从1开始打印
//        //  其次是2 （12%10）、 3（123%10）、4（1234%10）
//        System.out.println(n%10);
//    }// 图 21
//}

//递归执行过程分析
//递归的程序的执行过程不太容易理解, 要想理解清楚递归, 必须先理解清楚 "方法的执行过程", 尤其是 "方法执行结束之后, 回到调用位置继续往下执行“

//代码示例3 写一个递归方法，输入一个非负整数，返回组成它的数字之和. 例如，输入 1729, 则应该返回1+7+2+9，
//        它的和是19

//import java.util.Scanner;
//
//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);
//        int n = sc.nextInt();
//        System.out.println(sum(n));
//    }
//    public static int sum(int n){
//        if(n<10){
//            return n;
//        }
//        return n%10+sum(n/10);
//    }
//}// 图 22 ，这题跟上面那题几乎一样，就不讲了，自己琢磨。



//代码示例4 求斐波那契数列的第 N 项
// 1 1 2 3 5 8 13
// 从第三位开始，等于自身前两位数相加

//import java.util.Scanner;
//
//// 递归
//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);
//        int n = sc.nextInt();
//        System.out.println(fibonacci(n));
//    }
//    public static int fibonacci(int n){
//        if(n<3){
//            return 1;
//        }
//        return fibonacci(n-1) + fibonacci(n-2);
//    }
//}// 图23

// 注意用 递归的方法来求 斐波那契数，效率很低
// 图 24
// 一个数字重复出现，求 第 n位的斐波那契函数，n越大重复的数字越大
// 所以我不推荐使用递归去处理斐波那契数，因为面试中，是该方法，你绝对GG

// 这里我推荐 迭代 方法(循环)
//代码如下
// 1 1 2 3 5 8 13
// 从第三位开始，等于自身前两位数相加

//import java.util.Scanner;
//
//public class UseOfMethods {
//    public static void main(String[] args) {
//        Scanner sc = new Scanner(System.in);
//        int n = sc.nextInt();
//        fibonacci(n);
//        sc.close();
//    }
//    public static void fibonacci(int n){
//        if(n<3) {
//            System.out.println(1);
//        }else {
//            int a =1;
//            int b =1;
//            int c = 0;
//            for(int i=3;i<=n;i++){//从第三位开始，等于自身前两位数相加
//                c = a+b;
//                a=b;
//                b=c;
//                //  1 1 2 3 5
//                //  a b c
//
//                //  1 1 2 3 5
//                //    a b c
//
//                //  1 1 2 3 5
//                //      a b c
//            }
//            System.out.println(c);
//        }
//    }
//}// 图 25，只是计算简单加法，效率要高很多

// 递归小结
//递归是一种重要的编程解决问题的方式.
//        有些问题天然就是使用递归方式定义的(例如斐波那契数列, 二叉树等), 此时使用递归来解就很容易.
//        有些问题使用递归和使用非递归(循环)都可以解决. 那么此时更推荐使用循环, 相比于递归, 非递归程序更加高效.


import java.util.Scanner;

//实现代码: 青蛙跳台阶问题(提示, 使用递归)
//一只青蛙一次可以跳上 1 级台阶，也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法
//讲解图，由图可知跟斐波那契函数是一样的规律，从第三位开始，等于自身前两位数相加
// 那么就很简单了
public class UseOfMethods {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        frogJump(n);
    }
    public static  void frogJump(int n){
        if(n<3){
            System.out.println(n+"阶台阶"+"青蛙有"+n+"种跳法");
        }else{
            int a = 1;
            int b = 2;
            int c = 0;
            for(int i =3;i<=n;i++){
                c=a+b;
                a=b;
                b=c;
            }
            System.out.println(n+"阶台阶"+"青蛙有"+c+"种跳法");
        }
    }
}






